#include "cylinder.h"
#include <cmath>
#include <GL/gl.h>
#define ANGLE_DELTA 20.0
#define HEIGHT_DELTA 0.1
#define TO_RAD(x) ((x)*M_PI / 180.0)

Cylinder::Cylinder()
{
    buildLateral();
    buildBases();
}

bool Cylinder::intersect(const Ray &r, double &t, Vector &normal) const
{
    return false;
}

void Cylinder::openGlRender() const
{
    std::vector<Face>::const_iterator it;
    setup_gl_materials();

    // Draw lateral face
    glBegin(GL_QUADS);
    for(it = faces_.begin() ; it != faces_.end() ; it++) {
        const Face &f = *it;
        Vector normal = vecprod(*(f[1]->first) - *(f[0]->first),
                                *(f[2]->first) - *(f[0]->first));
        normal = -normal.normalize();
        glNormal3d(normal(0), normal(1), normal(2));
        glVertex3dv(f[0]->first->toCArray());
        glVertex3dv(f[1]->first->toCArray());
        glVertex3dv(f[2]->first->toCArray());
        glVertex3dv(f[3]->first->toCArray());
    }
    glEnd();

    // Draw top bases
    Vector top_base_normal = vecprod(*(top_base[1]->first) - *(top_base[0]->first),
                                     *(top_base[2]->first) - *(top_base[0]->first));
    top_base_normal.normalize();
    Vector bottom_base_normal = -top_base_normal;
    glBegin(GL_POLYGON);
        glNormal3d(top_base_normal(0), top_base_normal(1), top_base_normal(2));
        for(size_t i = 0 ; i < top_base.size() ; i++) {
            glVertex3dv(top_base[i]->first->toCArray());
        }
    glEnd();

    // Draw bottom base
    glBegin(GL_POLYGON);
        glNormal3d(bottom_base_normal(0), bottom_base_normal(1), bottom_base_normal(2));
        for(size_t i = 0 ; i < bottom_base.size() ; i++) {
            glVertex3dv(bottom_base[i]->first->toCArray());
        }
    glEnd();
}

void Cylinder::buildLateral()
{
    for(double height = 0.5 ; height >= -0.5 ; height -= HEIGHT_DELTA) {
        double heightnext = height - HEIGHT_DELTA;
        Edge *prev_edge = 0;

        for(double angle = 0.0 ; angle <= 360.0 ; angle += ANGLE_DELTA) {
            double radangle = TO_RAD(angle);
            Point *p1 = new Point(cos(radangle), height, sin(radangle));
            Point *p2 = new Point(cos(radangle), heightnext, sin(radangle));
            Edge *current_edge = new Edge(p2, p1);

            if( prev_edge ) {
                Face f;
                Edge *e2 = new Edge(current_edge->second, prev_edge->second);
                Edge *e3 = new Edge(prev_edge->second, prev_edge->first);
                Edge *e4 = new Edge(prev_edge->first, current_edge->first);

                f.push_back(current_edge);
                f.push_back(e2);
                f.push_back(e3);
                f.push_back(e4);
                faces_.push_back(f);
            }
            prev_edge = current_edge;
            vertices_.push_back(p1);
            vertices_.push_back(p2);
        }
    }
}

void Cylinder::buildBases()
{
    const size_t num_vertices_per_face = static_cast<size_t>((360.0 / ANGLE_DELTA));
    const size_t num_faces = faces_.size();

    for(size_t i = 0 ; i < num_vertices_per_face ; i++) {
        Face f1 = faces_[i];
        Face f2 = faces_[num_faces - i - 1];
        Edge *bottom_edge = new Edge(f2[3]->second, f2[3]->first);

        top_base.push_back(f1[1]);
        bottom_base.push_back(bottom_edge);
    }
}

ICgObject* Cylinder::clone() const
{
    Cylinder *c = new Cylinder();
    c->multiplyCurrentTransformation(this->transformation_);
    c->material_ = this->material_;
    return c;
}

#undef TO_RAD
#undef ANGLE_DELTA
#undef HEIGHT_DELTA
